// // Copyright (c) 2009 All Right Reserved // // Stephen Toub // stoub@microsoft.com // 2009-01-01 // Contains ... using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.Contracts; using System.Linq; using System.Xml.Serialization; using JetBrains.Annotations; using LargoCommon.Music; namespace LargoCommon.Midi { /// A collection of MIDI events. [Serializable] public sealed class MidiEventCollection : ICollection { //// : ICollection, ICloneable where T : new() { #region Fields /// /// Event List. /// private readonly List eventList; #endregion #region Constructors /// Initializes a new instance of the MidiEventCollection class. public MidiEventCollection() { this.eventList = new List(); } /// /// Initializes a new instance of the MidiEventCollection class. /// /// Midi channel. public MidiEventCollection(MidiChannel givenChannel) { this.eventList = new List(); this.Channel = givenChannel; } #endregion #region Public Properties /// /// Gets or sets name of the collection. /// /// Property description. public string Name { [UsedImplicitly] get; set; } /// /// Gets a value indicating whether IsReadOnly. /// /// General musical property. public bool IsReadOnly => false; /// /// Gets Count of events. /// /// General musical property. public int Count => this.EventList.Count; /// /// Gets or sets Midi Channel. /// /// Property description. public MidiChannel Channel { get; set; } /// /// Gets the break events. /// /// Returns value. public MidiEventCollection GetBreakEvents { get { var c = new MidiEventCollection(); foreach (var ev in this.EventList) { if (ev == null) { continue; } var eventType = ev.EventType; switch (eventType) { case "MetaTempo": { c.Add(ev); break; } case "MetaKeySignature": { c.Add(ev); break; } case "MetaTimeSignature": { c.Add(ev); break; } } } return c; } } /// /// Gets the tempo events. /// /// Returns value. public MidiEventCollection GetTempoEvents { get { var c = new MidiEventCollection(); foreach (var ev in this.EventList) { if (ev == null) { continue; } var eventType = ev.EventType; switch (eventType) { case "MetaTempo": { c.Add(ev); break; } } } return c; } } #endregion #region Private Properties /// Gets properties and their values. /// Property description. [XmlIgnore] private List EventList { get { Contract.Ensures(Contract.Result>() != null); if (this.eventList == null) { throw new InvalidOperationException("Null event list."); } return this.eventList; } } #endregion #region ICollection /// /// Add Midi Event. /// /// Midi Event. public void Add(IMidiEvent item) { this.EventList.Add(item); } /// /// Add Range of Midi Events. /// /// Collection of Midi events. public void AddRange(IEnumerable collection) { if (collection != null) { this.EventList.AddRange(collection); } } /// /// Add Range. /// /// Collection of Midi Events. public void AddRange(MidiEventCollection collection) { if (collection != null) { this.EventList.AddRange(collection); } } /// /// Clear collection. /// public void Clear() { if (this.Count == 0 || this.EventList.Count == 0) { return; } this.EventList.Clear(); } /// /// Contains Midi Event. /// /// Midi Event. /// Returns value. public bool Contains(IMidiEvent item) { return this.EventList.Contains(item); } /// /// Copy To array. /// /// Array of Midi events. /// Array Index. public void CopyTo(IMidiEvent[] array, int arrayIndex) { if (arrayIndex >= array.Length || arrayIndex + this.Count > array.Length) { return; } this.EventList.CopyTo(array, arrayIndex); } /// /// Copy To array. /// /// Array of Midi events. /// Array Index. [UsedImplicitly] public void CopyTo(Array array, int arrayIndex) { if (array == null) { return; } if (arrayIndex < 0) { return; } for (var i = arrayIndex; i < this.EventList.Count; i++) { if (i <= array.GetUpperBound(0)) { array.SetValue(this.EventList[i], i); } } //// this.eventList.CopyTo(array, arrayIndex); } /// /// Get Enumerator. /// /// Returns value. IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } /// /// Get Enumerator. /// /// Returns value. IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } /// /// Remove item. /// /// Midi Event. /// Returns value. public bool Remove(IMidiEvent item) { return this.EventList.Remove(item); } /// /// Event At index. /// /// Index of event. /// Returns value. public IMidiEvent ElementAt(int index) { Contract.Requires(index >= 0); return this.EventList.ElementAt(index); } #endregion /// /// Get Note On. /// /// MIDI instrument. /// The start time. /// Note loudness. /// /// Returns value. /// [UsedImplicitly] public VoiceNoteOn GetNoteOn(byte note, int startTime, byte loudness) { VoiceNoteOn eventOn; if (note > 0) { var velocity = (byte)(note > 0 ? MusicalProperties.VelocityOfLoudness(loudness) : 0); eventOn = new VoiceNoteOn(startTime, this.Channel, note, velocity); } else { const byte velocity = 0; eventOn = new VoiceNoteOn(startTime, this.Channel, note, velocity); } return eventOn; } /// /// Get Note Off. /// /// MIDI instrument. /// The midi time. /// /// Returns value. /// [UsedImplicitly] public VoiceNoteOff GetNoteOff(byte note, int midiTime) { //// byte pitchBend, MIDI pitch bend. //// ...Pitch bend value, 0..16383, 8192 is centered. const byte velocity = 0; var eventOff = new VoiceNoteOff(midiTime, this.Channel, note, velocity); return eventOff; } #region Older Interface /// Inserts value of metre. /// Rhythmical beat. Numerator of the time signature. /// Rhythmical base. Negative power of two, denominator of time signature. public void PutMetre(byte rhythmicBeat, byte rhythmicBase) { //// byte order //// number of MIDI clock ticks per metronome click (0x18 = 24 ticks/click) const byte ticks = 48; //// number of metronome clicks per 1/4 note (The number of notated 32nd notes per MIDI quarter note?) const byte clicks = 1; var ts = new MetaTimeSignature(0, rhythmicBeat, rhythmicBase, ticks, clicks); this.Add(ts); } /// Inserts tempo. /// MIDI tempo = metronome clicks per minute. public void PutTempo(int tempo) { //// byte order, Contract.Requires(tempo != 0); var tv = MetaTempo.MidiTempoBaseNumber / tempo; //// microseconds per metronome clicks 0 var evt = new MetaTempo(0, tv); this.Add(evt); } /// Inserts tempo. /// Metre numerator. /// Metre base. /// MIDI tempo. [UsedImplicitly] public void PutMetreAndTempo(byte rhythmicBeat, byte rhythmicBase, int tempo) { //// byte order, Contract.Requires(tempo != 0); this.PutMetre(rhythmicBeat, rhythmicBase); //// order this.PutTempo(tempo); } /// Selects instrument and channel of the track. /// Delta Time./// /// MIDI instrument. public void PutInstrument(long deltaTime, byte givenInstrument) { var pch = new VoiceProgramChange(deltaTime, this.Channel, (MidiMelodicInstrument)givenInstrument); this.Add(pch); } /// /// Puts the key signature. /// /// The delta time. /// The given key. /// The given tonal genus. public void PutKeySignature(long deltaTime, TonalityKey givenKey, TonalityGenus givenTonalGenus) { var signature = new MetaKeySignature(deltaTime, givenKey, givenTonalGenus); this.Add(signature); } #region Meta Messages /// /// Write meta message. /// /// The delta time. /// Meta message text. public void PutMetaText(long deltaTime, string text) { var ev = new MetaText(deltaTime, text); this.Add(ev); } /// Write meta message. /// Meta message text. public void PutMetaCopyright(string text) { var ev = new MetaCopyright(0, text); this.Add(ev); } #endregion /// /// Inserts one note into data. /// /// The start delta time. /// MIDI note. /// The stop delta time. /// Note loudness. /// If set to true [is from previous bar]. /// If set to true [is going to next bar]. public void PutNote( long startTime, byte note, long stopTime, byte loudness, bool isFromPreviousBar, bool isGoingToNextBar) { //// byte pitchBend, MIDI pitch bend. byte velocity = 0; if (note > 0) { velocity = MusicalProperties.VelocityOfLoudness(loudness); } if (!isFromPreviousBar) { var eventOn = new VoiceNoteOn(startTime, this.Channel, note, velocity); this.Add(eventOn); } //// Staccato, Legato, ... !?! if (isGoingToNextBar) { return; } //// 2013/11 test, was stopTime only (stopTime - startTime + 1) var eventOff = new VoiceNoteOff(stopTime, this.Channel, note, velocity); //// velocity 0? this.Add(eventOff); } #endregion #region Adding and Removing Events /// Adds a collection of MIDI event messages to the collection. /// The events to be added. /// The position at which the first event was added. [UsedImplicitly] public int AddRange(Collection messages) { //// virtual // Validate the input if (messages == null) { throw new ArgumentNullException(nameof(messages)); } if (messages.Count == 0) { return -1; } // Store the count of the list (the inserted position of the first new element). var insertionPos = this.Count; // Add the events this.EventList.AddRange(messages); // Return the position of the first return insertionPos; } #endregion #region Global operations /// /// Increase all the delta times with given value. /// /// Given Time. public void AddTimeToTotals(long givenTime) { for (var i = 0; i < this.Count; i++) { if (i >= this.EventList.Count) { continue; } if (this.EventList[i] == null) { continue; } if (this.EventList[i].StartTime + givenTime >= 0) { this.EventList[i].StartTime += givenTime; } } } /// Converts the delta times on all events to from delta times to total times. public void RecomputeAbsoluteTimes() { //// ConvertDeltasToTotals //// [VL], was internal Contract.Requires(this.Count > 0); if (this.EventList == null || this.EventList.Count == 0) { return; } //// Update all delta times to be total times var ev0 = this.EventList[0]; if (this.Count <= 0 || ev0 == null) { return; } var total = ev0.DeltaTime; ev0.StartTime = total; for (var i = 1; i < this.Count; i++) { if (i >= this.EventList.Count) { continue; } var ev = this.EventList[i]; if (ev == null) { continue; } total += ev.DeltaTime; //// ev.DeltaTime = total; ev.StartTime = total; } } /// Converts the delta times on all events from total times back to delta times. public void RecomputeDeltaTimes() { //// ConvertTotalsToDeltas // [VL], was internal if (this.Count == 0) { return; } //// Update all total times to be deltas long lastStartTime = 0; for (var i = 0; i < this.Count; i++) { if (i >= this.EventList.Count) { continue; } var evi = this.EventList[i]; if (evi == null) { continue; } var time = evi.StartTime - lastStartTime; evi.DeltaTime = time >= 0 ? time : 0; lastStartTime = evi.StartTime; } } #endregion #region Sorting /// Sorts the events based on deltaTime. public void SortByStartTime() { // [VL], was internal // Sort by delta time this.EventList.Sort(new EventComparer()); } #endregion #region Private methods /// /// Get Enumerator. /// /// Returns value. private IEnumerator GetEnumerator() { //// return null; // the actual implementation return this.EventList.GetEnumerator(); } #endregion #region Sorting (internal only) /// Enables comparison of two events based on delta times. private sealed class EventComparer : IComparer { #region Implementation of IComparer /// Compares two MidiEvents based on delta times. /// The first MidiEvent to compare. /// The second MidiEvent to compare. /// Returns -1 if x.StartTime is larger, 0 if they're the same, 1 otherwise. public int Compare(IMidiEvent x, IMidiEvent y) { // Get the MidiEvents var eventX = x; var eventY = y; // Make sure they're valid if (eventX == null) { throw new ArgumentNullException(nameof(x)); } if (eventY == null) { throw new ArgumentNullException(nameof(y)); } // Compare the delta times return eventX.StartTime.CompareTo(eventY.StartTime); } #endregion } #endregion } }